Union vs Intersection
Union vs Intersection | Primitives
Union
type A = string | number;
This declares that type A can be either a string or a number.
type A = string | number;
// ✅ Valid assignments
let value1: A = "hello"; // string is allowed
let value2: A = 42; // number is allowed
// ❌ Invalid assignments
let value3: A = true; // Error: boolean is not assignable
let value4: A = null; // Error: null is not assignable
Intersection
type A = string & number;
In TypeScript, an intersection type using & means a value must satisfy all types simultaneously. The & symbol means "and".
type A = string & number;
// ❌ No value can be assigned to this type
let value: A = "hello"; // Error
let value2: A = 42; // Error
let value3: A = ... // Nothing works!
Union vs Intersection | Objects
Union
With object types, unions are more complex because TypeScript uses structural typing and looks at the shape of objects
type Cat = { name: string; meow: () => void };
type Dog = { name: string; bark: () => void };
let pet: Cat | Dog;
// You can only access properties that exist on BOTH types
console.log(pet.name); // ✓ valid - both have 'name'
pet.meow(); // ✗ error - might be a Dog
pet.bark(); // ✗ error - might be a Cat
Discriminated Unions
The most powerful pattern for object unions is using a discriminant property - a literal type that distinguishes between union members:
type Cat = { type: "cat"; name: string; meow: () => void };
type Dog = { type: "dog"; name: string; bark: () => void };
type Pet = Cat | Dog;
function handlePet(pet: Pet) {
// Type narrowing based on the discriminant
if (pet.type === "cat") {
pet.meow(); // ✓ TypeScript knows it's a Cat
} else {
pet.bark(); // ✓ TypeScript knows it's a Dog
}
}
Intersection
With object types, intersections merge properties from all types
type Person = { name: string };
type Employee = { employeeId: number };
type Worker = Person & Employee;
const worker: Worker = {
name: "Alice",
employeeId: 123,
}; // ✅ Must have BOTH properties